/* $Id: FuzzyDate.java,v 1.14 2007/09/07 06:51:21 gigabass Exp $
 *
 * Copyright (c) Guido Ludwig. All rights reserved.
 * Use is subject to license terms.
 */
package de.guidoludwig.jtm.domain;

import java.io.Serializable;

import de.guidoludwig.jtm.util.StringUtil;

/**
 * Utility to cover dates with unknown parts
 * 
 * @author <a href="mailto:jtrade@gigabss.de">Guido Ludwig</a>
 * @version $Revision: 1.14 $
 */
public class FuzzyDate implements Comparable<FuzzyDate>, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private String year;

	private String month;

	private String day;


	/**
	 * Create a FuzzyDate with unknown values
	 */
	public FuzzyDate() {
		this("????-??-??");
	}

	/**
	 * Create a FuzyDate from an ISO Date String (Format yyyy-MM-dd)
	 * 
	 * @param isoString
	 */
	public FuzzyDate(String isoString) {
		setDate(isoString);
	}

	public void setDate(String isoString) {
		setYear(null);
		setMonth(null);
		setDay(null);
		if (StringUtil.isBlank(isoString)) {
			return;
		}
		setYear(isoString.substring(0, 4));
		setMonth(isoString.substring(5, 7));
		setDay(isoString.substring(8, 10));
	}

	public void setYear(String newYear) {
		validateYear(newYear);
		if (newYear == null || newYear.length() == 0) {
			year = "????";
		} else if (newYear.length() == 4) {
			year = newYear;
		} else if (newYear.startsWith("?")) {
			year = "??" + newYear;
		} else {
			char first = newYear.charAt(0);
			if (first < '5') {
				year = "20" + newYear;
			} else {
				year = "19" + newYear;
			}
		}
	}

	public void setMonth(String newMonth) {
		validateMonth(newMonth);
		if (newMonth == null || newMonth.length() == 0
				|| newMonth.startsWith("?")) {
			month = "??";
		} else if (newMonth.length() == 2) {
			month = newMonth;
		} else if (newMonth.equals("0")) {
			month = "1" + newMonth;
		} else {
			month = "0" + newMonth;
		}
	}

	public void setDay(String newDay) {
		validateDay(newDay);
		if (newDay == null || newDay.length() == 0 || newDay.startsWith("?")) {
			day = "??";
		} else if (newDay.length() == 2) {
			day = newDay;
		} else if (newDay.equals("0")) {
			day = "1" + newDay;
		} else {
			day = "0" + newDay;
		}
	}

	/**
	 * @return the Year as a 4 digit String if the year is unknown, ???? is
	 *         returned
	 */
	public String getYear() {
		return year;
	}

	public String getYear2() {
		return getYear().substring(2);
	}

	/**
	 * @return the Month as a 4 digit String if the month is unknown, ?? is
	 *         returned
	 */
	public String getMonth() {
		return month;
	}

	/**
	 * @return the day as a 2 digit String if the day is unknown, ?? is returned
	 */
	public String getDay() {
		return day;
	}

	/**
	 * FIXME : implement hashcode as well
	 * @return the Point in time as an ISO 8601 String (yyyy-MM-dd) unknown parts are
	 * returned as ?
	 */
	@Override
	public String toString() {
		return getYear() + "-" + getMonth() + "-" + getDay();
	}

	/**
	 * @param time
	 * @return the comparison of the string representations
	 */
	public int compareTo(FuzzyDate time) {
		if (time == null) {
			return 1;
		}
		int result = getYear().compareTo(time.getYear());
		if (result != 0) {
			return result;
		}
		result = getMonth().compareTo(time.getMonth());
		if (result != 0) {
			return result;
		}
		return getDay().compareTo(time.getDay());
	}

	public boolean equals(FuzzyDate o) {
		if (o == null) {
			return false;
		}
		return toString().equals(o.toString());
	}

	private void validateYear(String value) {
		validateAllowedChars(value);
		if (value != null && value.length() != 0 && value.length() != 2
				&& value.length() != 4) {
			throw new IllegalArgumentException("year " + value
					+ " must have 0,2 or 4 characters");

		}
	}

	private void validateMonth(String value) {
		validateAllowedChars(value);
		if (value != null && value.length() > 2) {
			throw new IllegalArgumentException("month " + value
					+ " must have 0 - 2 characters");
		}
		if (value != null && value.indexOf('?') < 0) {
			int intValue = Integer.valueOf(value).intValue();
			if (intValue < 1 || intValue > 12) {
				throw new IllegalArgumentException("month " + value
						+ " out of range 1-12");
			}
		}
	}

	private void validateDay(String value) {
		validateAllowedChars(value);
		if (value != null && value.length() > 2) {
			throw new IllegalArgumentException("day " + value
					+ " must have 0 - 2 characters");
		}
		if (value != null && value.indexOf('?') < 0) {
			int intValue = Integer.valueOf(value).intValue();
			if (intValue < 1 || intValue > 31) {
				throw new IllegalArgumentException("day " + value
						+ " out of range 1-31");
			}
		}
	}

	private void validateAllowedChars(String value) {
		if (value != null) {
			String allowedChars = "0123456789?";
			for (int i = 0; i < value.length(); i++) {
				char c = value.charAt(i);
				if (allowedChars.indexOf(c) < 0) {
					throw new IllegalArgumentException("value " + value
							+ " contains illegal characters");
				}
			}
		}
	}
}
